﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using BibMan.model.database.bibtex;
using System.Text.RegularExpressions;
using BibMan.util;
using System.Collections.ObjectModel;
using BibMan.controller;
using System.ComponentModel;

namespace BibMan.model.database {

    public class Database : IEnumerable<Reference>, IXmlSerializable {

        private readonly String PIN_XML = "Pin";
        private readonly String NAME_XML = "Name";
        private readonly String REFERENCE_XML = "Reference";

        public Reference this[String id] {
            get {
                return References[id];
            }
            set {
                if (!id.Equals(value.Id))
                    throw new ArgumentException("The id and lookup were not the same.");
                References[id] = value;
            }
        }
        public readonly IdentifiableList<String, Reference> References;
        public readonly ObservableDictionary<String, Pin> Pins;

        public Database() {
            this.References = new IdentifiableList<String, Reference>();
            References.Added += ReferenceAdded;
            References.Removed += ReferenceRemoved;
            References.Updated += ReferenceUpdated;
            this.Pins = new ObservableDictionary<String, Pin>();
        }

        private void ReferenceAdded(String id, Reference reference) {
            foreach (String pinName in reference.Pins) {
                if (!Pins.ContainsKey(pinName)) {
                    this.Pins.Add(pinName, new Pin());
                }
                this.Pins[pinName].References.Add(reference);
            }
        }
        private void ReferenceRemoved(String id, Reference reference) {
            foreach (Pin pin in Pins.Values) {
                pin.References.RemoveWhere(e => e.Id == id);
            }
        }
        private void ReferenceUpdated(String id, Reference oldRreference, Reference newReference) {
            // Add oldReference's pins to new reference
            foreach (String pinName in oldRreference.Pins) {
                newReference.Pins.Add(pinName);
            }
            // Add new reference's pins to the central store
            foreach (String pinName in newReference.Pins) {
                if (!Pins.ContainsKey(pinName)) {
                    this.Pins.Add(pinName, new Pin());
                }
                this.Pins[pinName].References.Add(newReference);
            }
        }
        
        
        public System.Xml.Schema.XmlSchema GetSchema() {
            return null;
        }

        public void ReadXml(System.Xml.XmlReader reader) {
            XmlSerializer referenceSerializer = new XmlSerializer(typeof(Reference));

            bool wasEmpty = reader.IsEmptyElement;
            reader.Read();

            if (wasEmpty)
                return;

            while (reader.LocalName == PIN_XML) {
                String name = reader.GetAttribute(NAME_XML);
                String notes = reader.ReadElementString();
                this.Pins.Add(name, new Pin(notes));
            }
            while (reader.NodeType != System.Xml.XmlNodeType.EndElement) {
                Reference reference = (Reference)referenceSerializer.Deserialize(reader);
                this.References.Add(reference.Id, reference);
                reader.ReadToNextSibling(REFERENCE_XML);
            }
            reader.ReadEndElement();
        }

        public void WriteXml(System.Xml.XmlWriter writer) {
            XmlSerializerNamespaces blankNamespace = new XmlSerializerNamespaces();
            blankNamespace.Add(string.Empty, string.Empty);

            XmlSerializer referenceSerializer = new XmlSerializer(typeof(Reference));
            
            foreach (String pin in Pins.Keys) {
                //writer.WriteElementString("Pin
                writer.WriteStartElement(PIN_XML);
                writer.WriteAttributeString(NAME_XML, pin);
                writer.WriteString(Pins[pin].Notes);
                writer.WriteEndElement();
                //pinsSerializer.Serialize(writer, pin, blankNamespace);
            }
            foreach (Reference reference in References.Values) {
                referenceSerializer.Serialize(writer, reference, blankNamespace);
            }
        }

        public IEnumerator<Reference> GetEnumerator() {
            foreach (Reference reference in References.Values) {
                yield return reference;
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public List<TOutput> ConvertAll<TOutput>(Converter<Reference, TOutput> converter) {
            return References.Values.ToList().ConvertAll<TOutput>(converter);
        }

        public String ToBibTex() {
            StringBuilder ret = new StringBuilder();
            foreach (Reference r in References.Values) {
                ret.AppendLine(r.BibItem.ToBibTex());
                ret.AppendLine();
            }
            return ret.ToString();
        }

    }
}
